#!/usr/bin/env python
#
# check_deps.py
#
# A helper script to check whether the dependencies of proposed binary packages are already present in either
# self proposal file (proposal.yml) or final accepted packages file (pkglist_<debian_code_name>.yml).
# if not present, then it will display all unmet dependency packages.
#
# Usage:
#   $ ./check_deps.py [proposal file]
#
# Copyright (c) 2019 TOSHIBA Corporation
#
# SPDX-License-Identifier: Apache-2.0
#
import sys
import os
import common


def display_unmet_dep(unmet_dep_dic):
    if len(unmet_dep_dic):
        print("\nThe following unmet dependencies found:")
        for src_pkg, dep_pkg_list in unmet_dep_dic.items():
            for dep_name, dep_info in dep_pkg_list.items():
                depend_by_str = ""
                for dep_item in dep_info:
                    depend_by_str = depend_by_str + dep_item[0] + " (in " + dep_item[1] + "), "
                print("\t" + dep_name + " (" + src_pkg + ") depended by " + depend_by_str)
    else:
        print('All dependencies are satisfied')


def get_unmet_dep(apt, pdp_info, prop_info):
    """
    Get any unmet dependencies in the proposal.
       All the binary package dependencies in the proposal should be either already proposed in proposal.yml file or
       already in the accepted list in pkglist_xxx.yml file

       If any missing dependencies found then it will display in the screen
       The following unmet dependencies found:
            binpkgA1 (srcpkgA) depended by binpkgX1 (in srcpkgX)
            binpkgB1 (srcpkgB) depended by binpkgY1 (in srcpkgY)

    :param apt: initialized apt object
    :param pdp_info: loaded pkglist object
    :param prop_info: loaded proposal object
    :type apt: type of common::Apt
    :type pdp_info: type of common::PDPInfo
    :type prop_info: type of common::PDPProposal
    :return: Dictionary of unmet dependency { dep_src_pkg : {dep_pkg_name: [(binpkgX1, srcpkgX), (binpkgY1, srcpkgY), ...], ...}}
    :rtype: type of Dictionary {str: {str : list(tuple(str, str))}}
    """
    # get all the proposed binary packages list in the proposal info
    prop_info_bin_pkg_list = [bp for bp_info in prop_info.proposed_src_pkgs.values() for bp in bp_info.bin_pkg_dict]

    # get all the accepted binary packages list from pdp info
    pdp_info_bin_pkg_list = [bp for bp_info in pdp_info.get_pdp_info().values() for bp in bp_info.bin_pkg_data_dict]

    # make union of all the binary packages, this is available binary package list
    avail_bin_pkg_list_union = list(set().union(prop_info_bin_pkg_list, pdp_info_bin_pkg_list))

    # check whether the dependency packages in the proposal are present in the final available binary package list
    unmet_dep_dict1 = dict()
    for src_pkg_name, bp_info in prop_info.proposed_src_pkgs.items():
        for bp_name, dep_list in bp_info.bin_pkg_dict.items():
            for dep in dep_list:
                # if it is virtual package, parse the provider package name
                if '>:' in dep:
                    dep = dep.split(':')[1]

                if dep not in avail_bin_pkg_list_union:
                    # if dependency package is not found in proposal + pkglist then add to unmet dictionary list
                    if dep in unmet_dep_dict1:
                        # append to the existing list
                        unmet_dep_dict1[dep].append((bp_name, src_pkg_name))
                    else:
                        # create new list
                        unmet_dep_dict1[dep] = [(bp_name, src_pkg_name)]

    # prepare unmet dependencies with their source package names
    unmet_dep_dict2 = dict()
    for dep_pkg, dep_pkg_info in unmet_dep_dict1.items():
        dep_src_pkg, dep_src_pkg_ver, dep_src_bin_pkg_list = apt.apt_cache_get_src_info(dep_pkg)
        if dep_src_pkg in unmet_dep_dict2:
            unmet_dep_dict2[dep_src_pkg].update({dep_pkg: dep_pkg_info})
        else:
            unmet_dep_dict2[dep_src_pkg] = {dep_pkg: dep_pkg_info}
    return unmet_dep_dict2


if __name__ == "__main__":
    prop_file = "proposal.yml"

    # process arguments
    if len(sys.argv) > 1:
        if any([opt in sys.argv[1:] for opt in ['-h', '--help']]):
            common.die('\t usage: check_deps.py [proposal file]')
        else:
            prop_file = sys.argv[1]

    # exit, if proposal.yml file doesn't exist
    if not os.path.exists(prop_file):
        common.die(prop_file + 'is not exist !')

    # load proposal.yml file, exit if it fails to
    proposal_info = common.PDPProposal().load(prop_file)
    if not proposal_info:
        common.die('fail to load proposal file')

    # load pkglist_<debian version>.yml file, exit if it fails to
    pdp_info_obj = common.PDPInfo(proposal_info.proposed_debian_version)
    pdp_info_obj.load_pdp()
    #common.die('fail to load pkglist file')

    # Initialize apt
    apt = common.Apt()
    if not apt.apt_initialize(proposal_info.proposed_debian_version):
        del apt
        common.die("Apt initialize is failed")

    # get unmet dependencies
    unmet_dep_dict = get_unmet_dep(apt, pdp_info_obj, proposal_info)

    # display unmet dependencies
    display_unmet_dep(unmet_dep_dict)
